package agent.model;

import jade.core.AID;
import jade.core.Agent;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import util.Constant;
import agent.behaviour.LiveBehaviour;
import agent.communication.AgentsCommunicationMsg;
import agent.enums.Ressource;
import agent.enums.TypeAgent;
import agent.model.royaume.AgentRoyaume;

/**
 * Classe abstraite représsant un agent.
 * @author Jean-François Augé & Guillaume Poças
 */
public abstract class AbstractAgent extends Agent {

	private static final long serialVersionUID = 1L;

	private TypeAgent agent;
	private boolean mort;
	private AgentRoyaume royaume;
	private LiveBehaviour comportement;

	private Ressource ressourceProduite;
	private double rythmeProduction;
	private double stockProductionCourant;
	private int stockProduitMax;
	private double prixProduit;
	private boolean productionEnCours;

	private Ressource ressourceConsommee;
	private double rythmeConsommation;
	private double stockConsommationCourant;
	private int stockConsommeMax;

	private double argent;
	private boolean phaseImpots;
	private double frequenceImpots;
	private double tauxImpots;

	private double satisfaction;
	private double satisfactionHaut;
	private double satisfactionBas;
	private double satisfactionTotal;

	private int cycleMax;

	// réponses aux propositions
	private int reponsePropMissing;
	private double[] prixProp;
	private double[] quantite;
	private AID[] marchandsContactes;
	private double prixLePlusBas;
	private int marchandChoisi;
	private double quantiteRequise;

	
	/**
	 * Initialise les variables globales des agents et enregistre le service proposé.
	 */
	@Override
	protected void setup() {
		// récupère les arguments passés lors de la création de l'agent
		//0 = Royaume, 1 = Ressource (optionnel)
		//2 = tempsCycle, 3 = nombreCycles, 4 = prix
		//5 = rythmeProduction, 6 = stockProductionCourant, 7 = stockProduitMax
		//8 = rythmeConsommation, 9 = stockConsommationCourant, 10 = stockConsommeMax
		//11 = argent, 12 = satisfactionHaut, 13 = satisfactionBas, 14 = satisfaction
		//15 = frequenceImpots, 16 = tauxImpots
		Object[] arguments = getArguments();

		setRoyaume((AgentRoyaume) arguments[0]);

		// variables spécifiques à l'agent
		setRythmeProduction((Double)arguments[5]);
		setStockProductionCourant((Double)arguments[6]);
		setStockProduitMax((int)arguments[7]);
		setPrixProduit((Double)arguments[4]);

		setRythmeConsommation((Double)arguments[8]);
		setStockConsommationCourant((Double)arguments[9]);
		setStockConsommeMax((int)arguments[10]);

		setArgent((Double)arguments[11]);
		setFrequenceImpots((Double)arguments[15]);
		setTauxImpots((Double)arguments[16]);

		//satisfaction à calculer dans le Behaviour
		setSatisfactionHaut((Double)arguments[12]);
		setSatisfactionBas((Double)arguments[13]);
		setSatisfaction((Double)arguments[14]);
		setSatisfactionTotal(0);

		setCycleMax((int)arguments[3]);
		setMort(false);
		setPhaseImpots(false);
		setProductionEnCours(true);

		setReponsePropMissing(0);

		// s'enregistre dans l'annuaire
		register();

		// informations de debug
		if(Constant.DEBUG) System.out.println(getLocalName()+" started!"+" "+getRessourceConsommee()+" -> "+getRessourceProduite());

		//0 = Royaume, 1 = Ressource (optionnel)
		//2 = tempsCycle, 3 = nombreCycles, 4 = prix
		//5 = rythmeProduction, 6 = stockProductionCourant, 7 = stockProduitMax
		//8 = rythmeConsommation, 9 = stockConsommationCourant, 10 = stockConsommeMax
		//11 = argent, 12 = satisfactionHaut, 13 = satisfactionBas, 14 = satisfaction
		//15 = frequenceImpots, 16 = tauxImpots
		/*System.out.println("Debug Param de l'agent "+this.getLocalName() + "\n"
				+ "nombreCycles = "+getCycleMax() + "\n"
				+ "prix = "+getPrixProduit() + "\n"
				+ "rythmeProd = "+getRythmeProduction() + "\n"
				+ "stockProdCourant = "+getStockProductionCourant() + "\n"
				+ "stockProdMax = "+getStockProduitMax() + "\n"
				+ "rythmeConso = "+getRythmeConsommation() + "\n"
				+ "stockConsoCourant = "+getStockConsommationCourant() + "\n"
				+ "stockConsoMax = "+getStockConsommeMax() + "\n"
				+ "argent = "+getArgent() + "\n"
				+ "satisHaut = "+getSatisfactionHaut() + "\n"
				+ "satisBas = "+getSatisfactionBas() + "\n"
				+ "satis = "+getSatisfaction() + "\n"
				+ "frequenceImpots = "+getFrequenceImpots() + "\n"
				+ "tauxImpots = "+getTauxImpots());*/
	}

	/**
	 * Enregistre le service.
	 */
	public void register() {
		ServiceDescription sd = new ServiceDescription();
		sd.setType(getRessourceProduite().toString());
		sd.setName(getRessourceProduite().toString());

		DFAgentDescription dfd = new DFAgentDescription();
		dfd.setName(getAID());
		dfd.addServices(sd);

		try {
			DFService.register(this, dfd);
		}
		catch (FIPAException fe) {
			fe.printStackTrace();
		}
	}

	/**
	 * Désenregistre le service.
	 */
	@Override
	public void takeDown() {
		try {
			DFService.deregister(this);
		}
		catch (Exception e) {}
	}

	/**
	 * Envoi un message à plusieurs destinataires.
	 * @param agents, les destinatires du message
	 * @param typeMsg, message
	 * @param message, le contenu du message
	 * @param exclude, les agents à exclure de la liste des destinataires
	 */
	public void sendMsg(AID[] agents, int typeMsg, String message, AID...exclude) {
		ACLMessage msg = new ACLMessage(typeMsg);
		for(AID agent : agents)
			if((exclude.length > 0)) {
				if(agent != exclude[0])
					msg.addReceiver(agent);
			}
			else
				msg.addReceiver(agent);
		msg.setContent(message);
		send(msg);
		if(Constant.DEBUG_COM) System.out.println(getLocalName()+" > "+message);
	}

	/**
	 * Envoi un message à un destinaire.
	 * @param agent, destinataire du message
	 * @param typeMsg, message
	 * @param message, contenu du message
	 */
	public void sendMsg(AID agent, int typeMsg, String message) {
		ACLMessage msg = new ACLMessage(typeMsg);
		msg.addReceiver(agent);
		msg.setContent(message);
		send(msg);
		if(Constant.DEBUG_COM) System.out.println(getLocalName()+" > "+message);
	}

	/**
	 * Termine un agent.
	 */
	public void closeAgent() {
		String cmd = AgentsCommunicationMsg.INFORM.makeCommand("dead");
		sendMsg(new AID(getRoyaume().getLocalName(), AID.ISLOCALNAME), ACLMessage.INFORM_REF, cmd);
		setMort(true);
		doDelete();
		if(Constant.DEBUG) System.err.println(getLocalName()+" lived "+(getCycle()-1)+" cycles");
	}

	/**
	 * Duplique un agent.
	 */
	public void cloneAgent() {
		double argentGarde = getArgent()/2;
		double argentDonne = getArgent()/2;
		double consommeGarde = Math.ceil(getStockConsommationCourant()/2);
		double consommeDonne = Math.floor(getStockConsommationCourant()/2);
		double produitGarde = Math.ceil(getStockProductionCourant()/2);
		double produitDonne = Math.floor(getStockProductionCourant()/2);

		String cmd = AgentsCommunicationMsg.INFORM.makeCommand("birth", getAgent().toString(), getRessourceProduite().toString(), String.valueOf(argentDonne), String.valueOf(consommeDonne), String.valueOf(produitDonne));
		sendMsg(new AID(getRoyaume().getLocalName(), AID.ISLOCALNAME), ACLMessage.INFORM_REF, cmd);
		if(Constant.DEBUG) System.err.println(getLocalName()+" cloning..");

		argent = argentGarde;
		stockConsommationCourant = consommeGarde;
		stockProductionCourant = produitGarde;
	}

	/**
	 * Envoi un message à l'AgentRoyaume pour créer un successeur.
	 */
	public void successor() {
		String cmd = AgentsCommunicationMsg.INFORM.makeCommand("birth", getAgent().toString(), getRessourceProduite().toString(), String.valueOf(getArgent()), String.valueOf(getStockConsommationCourant()), String.valueOf(getStockProductionCourant()));
		sendMsg(new AID(getRoyaume().getLocalName(), AID.ISLOCALNAME), ACLMessage.INFORM_REF, cmd);
		if(Constant.DEBUG) System.err.println(getLocalName()+" successor of "+getLocalName()+"..");
	}

	/**
	 * Auguemente la satisfaction de 50%.
	 * @return false si l'opération à échouée
	 */
	public boolean increaseHalfSatisfaction() {
		if((satisfaction+0.5) <= 1.0) {
			satisfaction += 0.5;
			//System.out.println(getLocalName()+" stock:"+getStockProductionCourant()+" halfSatisfaction");
			return true;
		}
		else {
			satisfaction = 1.0;
			return false;
		}
	}
	
	/**
	 * Auguemente la satisfaction.
	 * @return false si l'opération à échouée
	 */
	public boolean increaseSatisfaction() {
		if((satisfaction+Constant.defaultSatisfactionFacteurConstant) <= 1.0) {
			satisfaction += Constant.defaultSatisfactionFacteurConstant;
			//System.out.println(getLocalName()+" stock:"+getStockProductionCourant()+" incrSatisfaction");
			return true;
		}
		else {
			satisfaction = 1.0;
			return false; //trop de satisfaction = mauvaise utilisation
		}
	}
	
	/**
	 * Diminue la satisfaction.
	 * @return false si l'opération à échouée
	 */
	public boolean decreaseSatisfaction() {
		if(satisfaction >= 0) {
			satisfaction *= 1 - Constant.defaultSatisfactionFacteurExpo;
			return true;
		}
		else {
			satisfaction = 0;
			return false; //utilisation < 0 = mauvaise utilisation
		}
	}

	/**
	 * Achète une ressource.
	 * @return false si l'opération à échouée
	 */
	public boolean acheteRessource(Double prix, Double quantite) {
		if((getArgent() >= prix) && ((stockConsommationCourant+quantite) <= stockConsommeMax)) {
			setArgent(getArgent() - prix);
			stockConsommationCourant += quantite;
			return true;
		}
		else return false;
	}
	
	/**
	 * Vend une ressource.
	 * @return false si l'opération à échouée
	 */
	public boolean vendRessource(double prix, double quantite) {
		if(stockProductionCourant >= quantite) {
			setArgent(getArgent() + (prix * quantite));
			stockProductionCourant -= quantite;
			return true;
		}
		else return false;
	}

	/**
	 * Produit une ressource.
	 * @return false si l'opération à échouée
	 */
	public boolean produit() {
		if(productionEnCours){
			if((stockProductionCourant + rythmeProduction) <= stockProduitMax) {
				stockProductionCourant += rythmeProduction;
				return true;
			}
			else return false; //stock trop grand = mauvaise utilisation
		}
		else	return false; //ne produit pas car stock plein
	}
	
	/**
	 * Consomme une ressource.
	 * @return false si l'opération à échouée
	 */
	public boolean consomme() {
		if(stockConsommationCourant >= rythmeConsommation){
			stockConsommationCourant -= rythmeConsommation;
			return true;
		}
		else return false; //consommation superieure au stock = mauvaise utilisation
	}

	/**
	 * Baisse le prix de la ressource vendue.
	 */
	public void prixBaisse() {
		if((getPrixProduit() - Constant.defaultVariationPrix) > Constant.defaultPrixMin)
			prixProduit -= Constant.defaultVariationPrix;
	}
	/**
	 * Auguemente le prix de la ressource vendue.
	 */
	public void prixAugmente() {
		prixProduit += Constant.defaultVariationPrix;
	}

	// getters & setters

	public TypeAgent getAgent() { return agent; }
	public void setAgent(TypeAgent agent) { this.agent = agent; }

	public boolean isMort() {	return mort;	}
	public void setMort(boolean mort) {	this.mort = mort;	}

	public AgentRoyaume getRoyaume() {	return royaume;	}
	public void setRoyaume(AgentRoyaume royaume) {	this.royaume = royaume;	}

	public LiveBehaviour getComportement() { return comportement; }
	public void setComportement(LiveBehaviour comportement) { this.comportement = comportement; }

	public double getStockProductionCourant() {	return stockProductionCourant;	}
	public void setStockProductionCourant(double stockProductionCourant) {	this.stockProductionCourant = stockProductionCourant;	}

	public Ressource getRessourceProduite() { return ressourceProduite; }
	public void setRessourceProduite(Ressource ressourceProduite) { this.ressourceProduite = ressourceProduite; }

	public double getRythmeProduction() { return rythmeProduction; }
	public void setRythmeProduction(double rythmeProduction) { this.rythmeProduction = rythmeProduction; }

	public int getStockProduitMax() { return stockProduitMax; }
	public void setStockProduitMax(int stockProduitMax) { this.stockProduitMax = stockProduitMax; }

	public double getPrixProduit() { return prixProduit; }
	public void setPrixProduit(double prixProduit) { this.prixProduit = prixProduit; }

	public boolean isProductionEnCours() {	return productionEnCours;	}
	public void setProductionEnCours(boolean productionEnCours) {	this.productionEnCours = productionEnCours;	}

	public Ressource getRessourceConsommee() { return ressourceConsommee; }
	public void setRessourceConsommee(Ressource ressourceConsommee) { this.ressourceConsommee = ressourceConsommee; }

	public double getRythmeConsommation() { return rythmeConsommation; }
	public void setRythmeConsommation(double rythmeConsommation) { this.rythmeConsommation = rythmeConsommation; }

	public double getStockConsommationCourant() {	return stockConsommationCourant;	}
	public void setStockConsommationCourant(double stockConsommationCourant) {	this.stockConsommationCourant = stockConsommationCourant;	}

	public int getStockConsommeMax() { return stockConsommeMax; }
	public void setStockConsommeMax(int stockConsommeMax) { this.stockConsommeMax = stockConsommeMax; }

	public double getArgent() { return argent; }
	public void setArgent(double argent) {
		//ReentrantLock mutex = new ReentrantLock();
		Object mutex = new Object();

		synchronized (mutex) {
			this.argent = argent;
		}
	}

	public boolean isPhaseImpots() { return phaseImpots; }
	public void setPhaseImpots(boolean phaseImpots) { this.phaseImpots = phaseImpots; }

	public double getFrequenceImpots() { return frequenceImpots; }
	public void setFrequenceImpots(double frequenceImpots) { this.frequenceImpots = frequenceImpots; }

	public double getTauxImpots() { return tauxImpots; }
	public void setTauxImpots(double tauxImpots) { this.tauxImpots = tauxImpots; }

	public double getSatisfaction() { return satisfaction; }
	public void setSatisfaction(double satisfaction) { this.satisfaction = satisfaction; }

	public double getSatisfactionHaut() { return satisfactionHaut; }
	public void setSatisfactionHaut(double satisfactionHaut) { this.satisfactionHaut = satisfactionHaut; }

	public double getSatisfactionBas() { return satisfactionBas; }
	public void setSatisfactionBas(double satisfactionBas) { this.satisfactionBas = satisfactionBas; }

	public double getSatisfactionTotal() { return satisfactionTotal; }
	public void setSatisfactionTotal(double satisfactionTotal) { this.satisfactionTotal = satisfactionTotal; }

	public int getCycleMax() { return cycleMax; }
	public void setCycleMax(int cycleMax) { this.cycleMax = cycleMax; }

	public int getReponsePropMissing() { return reponsePropMissing; }
	public void setReponsePropMissing(int reponsePropMissing) { this.reponsePropMissing = reponsePropMissing; }

	public double[] getPrixProp() { return prixProp; }
	public void setPrixProp(int taille) { prixProp = new double[taille]; }
	public void setPrixProp(int index, double d) { prixProp[index] = d; }

	public double[] getQuantite() { return quantite; }
	public void setQuantite(int taille) { quantite = new double[taille]; }
	public void setQuantite(int index, double d) { quantite[index] = d; }

	public AID[] getMarchandsContactes() { return marchandsContactes; }
	public void setMarchandsContactes(AID[] marchandsContactes) { this.marchandsContactes = marchandsContactes; }
	public void setMarchandContactes(int index, AID d) { marchandsContactes[index] = d; }

	public double getPrixLePlusBas() { return prixLePlusBas; }
	public void setPrixLePlusBas(double prixLePlusBas) { this.prixLePlusBas = prixLePlusBas; }

	public int getMarchandChoisi() { return marchandChoisi; }
	public void setMarchandChoisi(int marchandChoisi) { this.marchandChoisi = marchandChoisi; }

	public double getQuantiteRequise() { return quantiteRequise; }
	public void setQuantiteRequise(double quantiteRequise) { this.quantiteRequise = quantiteRequise; }

	public int getCycle() { return getComportement().getTickCount(); }


}
